package ai.yue.open.service.cd.service;

import ai.yue.library.base.exception.ResultException;
import ai.yue.library.base.util.DateUtils;
import ai.yue.library.base.util.ExceptionUtils;
import ai.yue.library.base.util.ListUtils;
import ai.yue.library.base.util.StrUtils;
import ai.yue.library.base.view.R;
import ai.yue.library.base.view.Result;
import ai.yue.open.service.cd.entity.*;
import ai.yue.open.service.cd.forest.auth.AuthRancherApiForest;
import ai.yue.open.service.cd.service.notify.NotifyDingTalkService;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.dtflys.forest.http.ForestResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Clock;
import java.time.LocalDateTime;
import java.util.List;

/**
 * Rancher CD
 * 
 * @author	ylyue
 * @since	2018年11月12日
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DevopsService {

    final AuthRancherApiForest authRancherApiForest;
    final ConfRancherService confRancherService;
    final DockerImageService dockerImageService;
    final WorkloadService workloadService;
    final WorkloadRedeployLogService workloadRedeployLogService;
    final NotifyResultService notifyResultService;
    final NotifyDingTalkService notifyDingTalkService;

    /**
     * cd
     */
    @Transactional
    public Result<?> cd(String namespace, String workloadName, @Nullable String imageTag) {
        // 1. 获取Rancher配置
        List<ConfRancher> confRancherList = confRancherService.getConfRancherList();
        ConfRancher confRancher = null;
        for (ConfRancher confRancherTemp : confRancherList) {
            for (String namespaceTemp : confRancherTemp.getNamespaceList()) {
                if (namespaceTemp.equals(namespace)) {
                    confRancher = confRancherTemp;
                    break;
                }
            }
        }
        if (confRancher == null) {
            return R.errorPromptFormat("{}命名空间未找到对应的Rancher配置，请先在控制台中初始化。", namespace);
        }

        // 2. 获取工作负载
        Workload workload = workloadService.lambdaQuery()
                .eq(Workload::getNamespace, namespace)
                .eq(Workload::getWorkloadName, workloadName)
                .one();

        // 3. 重新部署
        if (workload == null) {
            workload = new Workload();
            workload.setWorkloadName(workloadName);
            workload.setNamespace(namespace);
            workload.setIsRedeploy(true);
        }
        Result<?> redeployResult = null;
        if (workload.getIsRedeploy()) {
            redeployResult = redeploy(confRancher, workload, imageTag);
            if (redeployResult.isFlag() == false) {
                return redeployResult;
            }
        }

        // 4. 更新镜像
        String imageAddr = workload.getImageAddr();
        if (imageTag == null) {
            imageTag = workload.getCurrentDeployImageTag();
        }
        String finalImageTag = imageTag;
        dockerImageService.lambdaQuery()
                .eq(DockerImage::getImageAddr, imageAddr)
                .oneOpt()
                .ifPresentOrElse(dockerImage -> {
                    // 更新镜像-添加版本
                    List<String> imageTags = dockerImage.getImageTags();
                    imageTags.add(0, finalImageTag);
                    dockerImage.setImageTags(ListUtils.distinctLinked(imageTags));
                    dockerImageService.updateById(dockerImage);
                }, () -> {
                    // 新增镜像
                    DockerImage dockerImage = new DockerImage();
                    dockerImage.setImageName(StrUtil.subAfter(imageAddr, "/", true));
                    dockerImage.setImageAddr(imageAddr);
                    dockerImage.setImageTags(ListUtil.toList(finalImageTag));
                    dockerImageService.save(dockerImage);
                });

        // 5. 更新工作负载
        workloadService.saveOrUpdate(workload);

        // 6. 插入工作负载重新部署日志
        WorkloadRedeployLog workloadRedeployLog = new WorkloadRedeployLog();
        workloadRedeployLog.setWorkloadId(workload.getId());
        workloadRedeployLog.setRedeployImageTag(imageTag);
        workloadRedeployLogService.save(workloadRedeployLog);

        // 7. 返回结果
        return redeployResult;
    }

    /**
     * 重新部署
     */
    public Result<?> redeploy(ConfRancher confRancher, Workload workload, @Nullable String imageTag) {
        // 1. 组装workloadApiUrl
        String rancherApiUrl = confRancher.getRancherApiUrl();
        if (rancherApiUrl.endsWith("/")) {
            rancherApiUrl = StrUtil.replaceLast(rancherApiUrl, "/", "");
        }
        String namespace = workload.getNamespace();
        String workloadName = workload.getWorkloadName();
        String workloadApiUrl = rancherApiUrl + "/" + namespace + "/" + workloadName;
        if (workload.getWorkloadApiUrl() == null) {
            workload.setWorkloadApiUrl(workloadApiUrl);
        }

        // 2. 发送重新部署请求
        OapiRobotSendResponse oapiRobotSendResponse = null;
        Boolean isNotify = workload.getIsNotify();
        if (isNotify == null) {
            isNotify = false;
        }
        String exceptionResult = "";
        try {
            // 发送重新部署请求
            redeployRequest(workloadApiUrl, confRancher, workload, imageTag);
            // 发送钉钉通知
            if (isNotify == true) {
                oapiRobotSendResponse = notifyDingTalkService.sendLinkMessage(workloadApiUrl, workload, imageTag);
            }
        } catch (ResultException e) {
            // 404异常向上抛
            throw e;
        } catch (Exception e) {
            if (isNotify == true) {
                oapiRobotSendResponse = notifyDingTalkService.sendTextMessage(workloadApiUrl, workload, imageTag);
            }
            // 打印相关异常
            e.printStackTrace();
            exceptionResult = ExceptionUtils.getPrintExceptionToStr(e);
        }

        // 3. 打印重新部署日志
        String resultMsg = null;
        if (oapiRobotSendResponse != null) {
            boolean success = oapiRobotSendResponse.isSuccess();
            Long errcode = oapiRobotSendResponse.getErrcode();
            String errmsg = oapiRobotSendResponse.getErrmsg();
            JSONObject notifyInfo = new JSONObject();
            notifyInfo.put("success", success);
            notifyInfo.put("errcode", errcode);
            notifyInfo.put("errmsg", errmsg);
            resultMsg = StrUtil.format("【钉钉】通知结果：{}", notifyInfo.toString());
            Console.log(resultMsg);

            NotifyResult notifyResult = new NotifyResult();
            notifyResult.setSuccess(success);
            notifyResult.setNotifyResult(resultMsg);
            notifyResult.setExceptionResult(exceptionResult);
            notifyResultService.save(notifyResult);
        }

        // 4. 返回结果
        if (StrUtil.isNotBlank(exceptionResult)) {
            return R.internalServerError(exceptionResult);
        }
        return R.success(resultMsg);
    }

    /**
     * 重新部署请求
     *
     * @param workloadApiUrl 需要重新部署的工作负载API地址
     * @param confRancher    rancher 配置
     * @param imageTag       docker镜像版本
     */
    private void redeployRequest(String workloadApiUrl, ConfRancher confRancher, Workload workload, @Nullable String imageTag) {
        // 1. 获取bearerToken
        String bearerToken = "Bearer " + confRancher.getBearerToken();

        // 2. 获取工作负载信息
        ForestResponse<JSONObject> workloadResponse = authRancherApiForest.getWorkload(workloadApiUrl, bearerToken);
        if (workloadResponse.getStatusCode() == 404) {
            throw new ResultException("工作负载不存在，请核对 namespace: {} 与 workloadName: {} 是否正确", workload.getNamespace(), workload.getWorkloadName());
        } else if (workloadResponse.isError()) {
            String okResponse = ReflectUtil.getFieldValue(workloadResponse, "okResponse").toString();
            throw new ResultException(okResponse);
        }
        JSONObject workloadInfo = workloadResponse.getResult();

        // 3. 替换metadata时间标签，使工作负载执行重新部署
        JSONObject template = workloadInfo.getJSONObject("spec").getJSONObject("template");
        JSONObject metadata = template.getJSONObject("metadata");
        JSONObject annotations = metadata.getJSONObject("annotations");
        String cattle_io_timestamp = LocalDateTime.now(Clock.systemUTC()).format(DateUtils.FORMATTER) + "Z";
        if (annotations == null) {
            annotations = new JSONObject();
            annotations.put("cattle.io/timestamp", cattle_io_timestamp);
            metadata.put("annotations", annotations);
        } else {
            annotations.replace("cattle.io/timestamp", cattle_io_timestamp);
            metadata.replace("annotations", annotations);
        }

        // 4. 替换镜像版本
        JSONArray containers = template.getJSONObject("spec").getJSONArray("containers");
        JSONObject container = containers.getJSONObject(0);
        String image = container.getString("image");
        int index = image.lastIndexOf(':');
        String imageAddr = image.substring(0, index);
        workload.setImageAddr(imageAddr);
        if (StrUtils.isNotEmpty(imageTag)) {
            // 如果版本信息不为空，替换镜像值
            container.replace("image", imageAddr + ":" + imageTag);
            workload.setCurrentDeployImageTag(imageTag);
        } else {
            if (StrUtils.isEmpty(workload.getCurrentDeployImageTag())) {
                // 如果版本信息为空，则获取镜像版本
                String imageTagFromImage = image.substring(index + 1);
                workload.setCurrentDeployImageTag(imageTagFromImage);
            }
        }

        // 5. 重新部署工作负载
        ForestResponse<String> actRedeployResponse = authRancherApiForest.actRedeploy(workloadApiUrl, bearerToken, workloadInfo);
        if (actRedeployResponse.getStatusCode() != 200) {
            throw new ResultException("工作负载重新部署失败，请核对 namespace: {} 与 workloadName: {} 是否正确", workload.getNamespace(), workload.getWorkloadName());
        }
    }

}
